import {initState} from './initState';
import { Watcher } from './observer/watcher';
import { baseCompile } from './compiler/index';
import {createElement, createElm} from './vdom/index'
import { insertBefore, removeChild } from './vdom/node-ops';
import {patchVnode} from './vdom/patch'
import {isReservedTag, isUndef, normalizeProps} from './help'
import {VNode} from './vdom/index'
import {initEvent} from './initEvent'
import { nextTick } from './nextTick';
import {initRender} from './initRender'
export function initMixin(Vue){
    Vue.prototype._init = function(options){
        const vm = this;
        /*
        _parent: vnode.ctorOptions,
        options: CtorOptions
        */
        if (options && options._isComponent){
            // 组件需要来自父级的数据，如propData, listener
            vm.$options = options.options;
            vm.$options.propData = options._parent.propData;
            vm.$options.listeners = options._parent.listeners;
            vm.$options._renderChildren = options._parent.children || {};
            vm.$options.slotScopes = options._parentVnode.data.slotScopes || {};
        }else{
            vm.$options = options;
        }
        // 将父级的listener定义到当前vm._events;
        initEvent(vm);

        // 处理插槽
        initRender(vm);
        // 初始化状态
        initState(vm);

        if (vm.$options.el){
            /*
            1. 将模版编译成 render函数
                html:<div>{{count}}</div>
                render: with(this){return _c('div', {}, [_v(count)])}

            2. new Watcher(vm, vm._update(render.call(vm)), ()=>{});
                2.1 render.call(vm) 生成虚拟dom
                    虚拟dom: {
                        tag: 'div',
                        children: [
                            {
                                text: 1
                            }
                        ]
                    }
                2.2 vm._update
                    第一次渲染, 根据虚拟dom渲染对应的dom
                    不是第二次渲染, 根据当前的虚拟dom以及之前的虚拟dom进行diff, 便diff便修改dom.
                2.3 当 vm.count 变化的时候
                    vm.count 之前收集的watcher 中的 vm._update(render.call(vm)) 重新运行。
                    触发渲染
            */
           
            vm.$mount(vm.$options.el);
        };
    };

    
    Vue.prototype.$mount = function(el){
        const vm = this;
        const $options = vm.$options;
        if (el){
            el = document.querySelector(el);
            vm.$el = el;
        }
        // render 优先级 render > template > el
        if (!$options.render){
            let template;
            if ($options.template){
                template = $options.template;
            }else if ($options.el){
                template = el.outerHTML;
            }
            // 模版编译成 render函数
            $options.render = baseCompile(template);
        }
        vm._render = $options.render;
        const updateComponent = ()=>{
            vm._update(vm._render())
        };
        const watcher = new Watcher(vm, updateComponent, ()=>{});
        vm._watcher = watcher;
    };


    Vue.prototype._update = function(vnode){
        console.log(vnode);
        const vm = this;
        const prevNode = vm._vnode;
        if (!prevNode){
            // 第一次渲染(这里vue还判断了根元素是不是一样的, 如果不是, 就要销毁所有dom全部重新创建)
            const el = vm.$el;
            // 根据vnode全量生成dom
            const dom = createElm(vnode, el);
            vm.$el = vnode.elm;
            if (el){
                // 根组件才有，子组件自己创建完需要自行插入dom;
                const container = el.parentNode;
                insertBefore(container, dom, el);
                // 移除以前的占位
                removeChild(container, el);
            }
            
        }else{
            // 更新
            console.log('更新');
            patchVnode(prevNode, vnode);
            vm.$el = vnode.elm;
        }
        vm._vnode = vnode;
        console.log(vm);

    };

    // vue为了更快的速度访问, 将_c放在了实例上。这边为了好理解就这样写了。
    /*
    with(this){return _c('h1', {on:{'click': function($event){aaa++}}}, [_s(aaa)])}
    
    */
    Vue.prototype._c = function(tag, data, children){
        children = children.flat(Infinity)
        const vm = this;
        if (isReservedTag(tag)){
            // 是dom元素

            return createElement(vm, tag, data, children);
        }
        const components = vm.$options.components || {};
        if (isUndef(components[tag])){
            throw `这个组件[${tag}]没有定义哦`
        }
        

        const Ctor = components[tag];
        const CtorOptions = Ctor.options;

        // 格式化props
        /*
        ['aaa']
        {
            aaa: {type: null}
        }
        */
        // const propData = extractPropsFromVnode(CtorOptions.props);
        // console.log(CtorOptions, data);
        const propData = data.props;
        const listeners = data.on;
        const vnode = new VNode(vm, 'vue-component-'+tag, {...data}, undefined, false, {
            Ctor,
            CtorOptions,
            propData,
            listeners,
            children
        });
        return vnode;

    };
    // 文本
    Vue.prototype._v = function(value){
        return createElement(this, undefined, undefined, value);
    };
    // _s(count) 模版数据
    Vue.prototype._s = function(value){

        if (value != null){
            value = JSON.stringify(value);
        }
        return createElement(this, undefined, undefined, value);
    };
    Vue.prototype._e = function(){
        return createElement(this, undefined, undefined, '', true);

    };
    // 渲染列表
    Vue.prototype._l = function(items, cb){
        const vm = this;
        let vnodes = [];
        for (let i=0; i<items.length;i++){
            vm.$index = i;
            vnodes.push(cb.call(vm, items[i], i));
        }
        delete vm.$index;
        return vnodes;
    };


    Vue.prototype._t = function(slotName, arg){
        const vm = this;
        // 先在作用域插槽里找
        const slotFns = vm.$scopeSlots[slotName];
        if (slotFns){
            const vnodes = slotFns.map((slotFn)=>slotFn.call(vm, arg));
            return vnodes;
        }
        return vm.$slots[slotName];
    };
    Vue.prototype._u = function(fnArray){
        /**
         @params fnArray _u([key: 'footer', fn: function({items}){return _c('div', items)}])
         @return : {foot: fn}
         */
        const res = {};
        for (let i=0; i<fnArray.length; i++){
            const {key, fn} = fnArray[i];
            res[key] = fn;
        }
        return res;

    };
    Vue.extend = function(extendOptions){
        const Sub = function VueComponent(options){
            this._init(options)
        }
        // 组件名字
        const name = extendOptions.name;
        // 继承Vue
        Sub.prototype = Vue.prototype;
        Sub.prototype.constructor = Sub;
    
        Sub.options = extendOptions;
        Sub.options.components = Sub.options.components || {};
        

        /*转化props*/
        /*
        props: ['a-A'] 变成这样的 props: {'aA': {type: null}}
        */
        normalizeProps(extendOptions);

        // 方便递归查找查找
        Sub.options.components[name] = Sub;
        return Sub;
    };
    Vue.component = function(name, ctorOptions){
        ctorOptions.name = name;
        return Vue.extend(ctorOptions);
    };

    Vue.prototype.$on = function(event, fn){
        // vm.$on('add', ()=>{});
        const vm = this;
        vm._events = vm._events || {[event]: []};
        if (!Array.isArray(fn)){
            fn = [fn]
        }
        vm._events[event].push(...fn);
    };

    Vue.prototype.$emit = function(event, ...args){
        const vm = this;
        vm._events[event].forEach(fn=>{
            fn.apply(vm, args);
        });
    };

    
    Vue.prototype.$nextTick = function(fn){
        return nextTick(fn);
    };
    Vue.prototype.$watch = function(vm, expression, callback){
        return new Watcher(vm, expression, callback);
    };
}